using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Burst;
using Unity.Mathematics;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Rendering;

/*
    This class handle rendering of ground cells & debris using BRG.
    Both ground cells & debris could be rendered using the same GPU data layout:
        - obj2world matrix ( 3 * float4 )
        - world2obj matrix ( 3 * float4 )
        - color ( 1 * float4 )

    so 7 float4 per mesh.

    Do not forget data is stored in SoA

*/


public unsafe class BRG_Container
{
    // GLES平台: 使用UBO模式，有大小限制，需要分割成多个"窗口"
        //UBO（Uniform Buffer Object）：一种在 OpenGL 中传递大量 uniform 数据给 shader 的方法。它有大小限制（例如 OpenGL ES 3.0 中通常是 16KB 或 64KB）。
    // 现代平台: 使用SSBO模式，单一大缓冲区，性能更好
        //SSBO（Shader Storage Buffer Object）：在 OpenGL 4.3+ 或 Vulkan 中使用的一种更灵活的数据传输方式，可以传输更大量的数据（通常可以达到几MB或更多），并支持读写。
        
    //GLES平台: 使用Constant Buffer (UBO)
    // 有大小限制，需要分割成多个窗口, 每窗口大小: BatchRendererGroup.GetConstantBufferMaxWindowSize()
    private bool UseConstantBuffer => BatchRendererGroup.BufferTarget == BatchBufferTarget.ConstantBuffer;
    
    private bool m_CastShadows;
    private bool m_Initialized;


    //缓冲区管理
    private int m_MaxInstances;                         //容器最大实例数量（用户想展示的示例数量）
    private int m_InstanceCount;                        //当前实例数量
    private NativeArray<float4> m_SysmemBuffer;         //系统内存缓冲区（NativeArray<float4>）
    private GraphicsBuffer m_GPUPersistentInstanceData; //GPU持久化实例数据缓冲区(could be SSBO or UBO)
    //窗口化管理（UBO模式）
    private int m_AlignedGPUWindowSize;     //GPU窗口大小（字节对齐）
    private int m_MaxInstancePerWindow;     //每窗口最大实例数
    private int m_WindowCount;              //窗口数量(1 in SSBO mode, n in UBO mode)
    private int m_TotalGpuBufferSize;       //总GPU缓冲区大小
    
    //BRG核心对象
    private BatchRendererGroup m_BatchRendererGroup; //BRG主对象
    private BatchID[] m_BatchIDs;                    //批次ID数组（每窗口一个）
    private BatchMaterialID m_MaterialID;           //材质ID
    private BatchMeshID m_MeshID;                   //网格ID


    public const int InstanceSize = (3 * 2 + 1) * 16;//单个实例大小(字节) 两个3*3矩阵+颜色

    /// <summary>
    /// 初始化BRG容器
    /// </summary>
    /// <param name="mesh">网格</param>
    /// <param name="mat">材质</param>
    /// <param name="maxInstances">设置需要展示的实例个数</param>
    /// <param name="castShadows">是否接收阴影</param>
    /// <returns></returns>
    public bool Init(Mesh mesh, Material mat, int maxInstances, bool castShadows)
    {
        //userContext: 传递给 BRG 的用户自定义上下文，没有上下文传IntPtr.Zero，它会被传递给 OnPerformCulling 方法
        m_BatchRendererGroup = new BatchRendererGroup(this.OnPerformCulling, IntPtr.Zero);
        
        m_InstanceCount = 0;
        m_MaxInstances = maxInstances;
        m_CastShadows = castShadows;
        
        if (UseConstantBuffer)
        {
            //GLES平台: 使用Constant Buffer (UBO) ,有大小限制，需要分割成多个窗口, 每窗口大小: BatchRendererGroup.GetConstantBufferMaxWindowSize()
            m_AlignedGPUWindowSize = BatchRendererGroup.GetConstantBufferMaxWindowSize();
            m_MaxInstancePerWindow = m_AlignedGPUWindowSize / InstanceSize;
            m_WindowCount = (m_MaxInstances + m_MaxInstancePerWindow - 1) / m_MaxInstancePerWindow;
            m_TotalGpuBufferSize = m_WindowCount * m_AlignedGPUWindowSize;
            m_GPUPersistentInstanceData = new GraphicsBuffer(GraphicsBuffer.Target.Constant, m_TotalGpuBufferSize / 16, 16);
        }
        else
        {
            // 其他平台: 使用Raw Buffer (SSBO)  单一大缓冲区，无窗口分割  更高效的数据访问
            m_AlignedGPUWindowSize = (m_MaxInstances * InstanceSize + 15) & (-16);
            m_MaxInstancePerWindow = maxInstances;
            m_WindowCount = 1;
            m_TotalGpuBufferSize = m_WindowCount * m_AlignedGPUWindowSize;
            m_GPUPersistentInstanceData = new GraphicsBuffer(GraphicsBuffer.Target.Raw, m_TotalGpuBufferSize / 4, 4);
        }
        //元数据配置
        //创建一个包含3个元素的临时数组，用于描述每个渲染实例的GPU数据布局,这3个元素分别对应：物体变换矩阵、逆变换矩阵、颜色
        //GPU数据布局 每个渲染实例需要7个float4数据:  物体到世界坐标变换矩阵(3 * float4)  世界到物体坐标变换矩阵(3 * float4)  颜色信息(1 * float4)
        var batchMetadata = new NativeArray<MetadataValue>(3, Allocator.Temp, NativeArrayOptions.UninitializedMemory);//NativeArrayOptions.UninitializedMemory: 不初始化内存，性能更好
        //获取着色器属性ID,这些ID用于在GPU缓冲区中标识不同的数据块
        int objectToWorldID = Shader.PropertyToID("unity_ObjectToWorld");//Unity内置的物体到世界坐标变换矩阵
        int worldToObjectID = Shader.PropertyToID("unity_WorldToObject");//Unity内置的世界到物体坐标变换矩阵
        int colorID = Shader.PropertyToID("_BaseColor");
        //创建系统内存中的GPU缓冲区镜像,作为CPU端数据的暂存区，便于批量更新GPU数据(m_totalGpuBufferSize:缓冲区大小（以float4为单位 = 16字节))
        //Allocator.Persistent: 持久化分配器，生命周期与对象相同
        //NativeArrayOptions.ClearMemory: 清零内存，确保初始状态干净
        m_SysmemBuffer = new NativeArray<float4>(m_TotalGpuBufferSize / 16, Allocator.Persistent, NativeArrayOptions.ClearMemory);
        
        //批次注册循环
        // 为什么需要循环？
        //      UBO模式: 受常量缓冲区大小限制，需要分割成多个窗口
        //      SSBO模式: 虽然只有一个窗口，但保持代码统一性
        m_BatchIDs = new BatchID[m_WindowCount];
        for (int b = 0; b < m_WindowCount; b++)
        {
            //这里就是等于在给shader属性赋值，shader需要什么数据就从这里拿，还可以加一些自定义数据...
            batchMetadata[0] = CreateMetadataValue(objectToWorldID, 0, true);                               //物体变换矩阵
            batchMetadata[1] = CreateMetadataValue(worldToObjectID, m_MaxInstancePerWindow * 3 * 16, true); //逆变换矩阵
            batchMetadata[2] = CreateMetadataValue(colorID, m_MaxInstancePerWindow * 3 * 2 * 16, true);     //颜色
            // 布局: [所有变换矩阵][所有逆变换矩阵][color0, color1, ..., colorN]
            int offset = b * m_AlignedGPUWindowSize;
            m_BatchIDs[b] = m_BatchRendererGroup.AddBatch(
                batchMetadata,                              //描述数据布局的元数据数组
                m_GPUPersistentInstanceData.bufferHandle,   //GPU缓冲区的原生句柄
                (uint)offset,                               //当前窗口在整个缓冲区中的字节偏移量
                UseConstantBuffer ? (uint)m_AlignedGPUWindowSize : 0    //窗口大小
            );
        }
        //释放临时分配的元数据数组
        batchMetadata.Dispose();
        
        //设置全局边界，避免被整体剔除
        //1048576=2^20  足够大;2的幂次: 计算机友好的数值，便于内存对齐和计算优化;避免溢出: 在float精度范围内，不会导致数值精度问题
        UnityEngine.Bounds bounds = new Bounds(new Vector3(0, 0, 0), new Vector3(1048576.0f, 1048576.0f, 1048576.0f));
        // 超大边界框的优势:简单粗暴: 一次设置，无需更新  确保覆盖: 保证所有实例都在边界内  避免剔除: 防止BRG被整体剔除  性能优化: 避免复杂的边界计算
        // 如果使用精确的边界框：计算复杂: 需要遍历所有实例计算真实边界  动态更新: 实例位置改变时需要重新计算边界  性能开销: 频繁的边界框更新影响性能
        // 潜在的副作用:可能导致阴影质量下降或性能问题  某些光照剔除算法可能受到影响  可能导致不必要的光照计算  反射探针: 可能影响反射探针的选择  遮挡剔除: 可能干扰遮挡剔除的效果  LOD系统: 可能影响LOD的距离计算
        m_BatchRendererGroup.SetGlobalBounds(bounds);
        
        // Register mesh and material
        if (mesh) 
            m_MeshID = m_BatchRendererGroup.RegisterMesh(mesh);
        if (mat) 
            m_MaterialID = m_BatchRendererGroup.RegisterMaterial(mat);
        m_Initialized = true;
        return true;
    }
    
    /// <summary>
    /// 创建元数据值，告诉BRG如何解释GPU缓冲区中的数据,使用位掩码标记是否为每实例数据
    /// </summary>
    /// <param name="nameID">着色器属性ID</param>
    /// <param name="gpuOffset"> 在GPU缓冲区中的字节偏移量</param>
    /// <param name="isPerInstance">是否为每实例数据（true表示每个实例都有不同值）</param>
    /// <returns></returns>
    private static MetadataValue CreateMetadataValue(int nameID, int gpuOffset, bool isPerInstance)
    {
        // 第31位: 标记是否为每实例数据
        // 低31位: 存储GPU缓冲区偏移量
        const uint kIsPerInstanceBit = 0x80000000;
        return new MetadataValue
        {
            NameID = nameID,
            Value = (uint)gpuOffset | (isPerInstance ? (kIsPerInstanceBit) : 0),
        };
    }


    //Because of SoA and this class is managing 3 BRG properties ( 2 matrices & 1 color ), the last window could use up to 3 SetData
    /// <summary>
    /// 向GPU上传实例数据
    /// </summary>
    /// <param name="instanceCount">实例数量</param>
    /// <returns></returns>
    [BurstCompile]
    public bool UploadGpuData(int instanceCount)
    {
        if ((uint)instanceCount > (uint)m_MaxInstances)
            return false;
        //窗口分批处理
        m_InstanceCount = instanceCount;
        int completeWindows = m_InstanceCount / m_MaxInstancePerWindow;

        //如果有完整窗口，一次性上传所有完整窗口的数据
        if (completeWindows > 0)
        {
            int sizeInFloat4 = (completeWindows * m_AlignedGPUWindowSize) / 16;//m_alignedGPUWindowSize是对齐后的GPU窗口大小
            m_GPUPersistentInstanceData.SetData(m_SysmemBuffer, 0, 0, sizeInFloat4);
        }

        //处理最后一个不完整窗口
        int lastBatchId = completeWindows;
        int itemInLastBatch = m_InstanceCount - m_MaxInstancePerWindow * completeWindows;

        if (itemInLastBatch > 0)
        {
            int windowOffsetInFloat4 = (lastBatchId * m_AlignedGPUWindowSize) / 16;
            int offsetMat1 = windowOffsetInFloat4 + m_MaxInstancePerWindow * 0;
            int offsetMat2 = windowOffsetInFloat4 + m_MaxInstancePerWindow * 3;
            int offsetColor = windowOffsetInFloat4 + m_MaxInstancePerWindow * 3 * 2;
            m_GPUPersistentInstanceData.SetData(m_SysmemBuffer, offsetMat1, offsetMat1, itemInLastBatch * 3);     // 3 float4 for obj2world
            m_GPUPersistentInstanceData.SetData(m_SysmemBuffer, offsetMat2, offsetMat2, itemInLastBatch * 3);    // 3 float4 for world2obj
            m_GPUPersistentInstanceData.SetData(m_SysmemBuffer, offsetColor, offsetColor, itemInLastBatch * 1);     // 1 float4 for color
        }

        return true;
    }

    //释放所有资源
    public void Shutdown()
    {
        if (m_Initialized)
        {
            for (uint b = 0; b < m_WindowCount; b++)
                m_BatchRendererGroup.RemoveBatch(m_BatchIDs[b]);
            
            m_BatchRendererGroup.UnregisterMaterial(m_MaterialID);
            m_BatchRendererGroup.UnregisterMesh(m_MeshID);
            m_BatchRendererGroup.Dispose();
            m_GPUPersistentInstanceData.Dispose();
            m_SysmemBuffer.Dispose();
        }
    }

    // return the system memory buffer and the window size, so BRG_Background and BRG_Debris can fill the buffer with new content
    public NativeArray<float4> GetSysmemBuffer(out int totalSize, out int alignedWindowSize)
    {
        totalSize = m_TotalGpuBufferSize;
        alignedWindowSize = m_AlignedGPUWindowSize;
        return m_SysmemBuffer;
    }

    
    // Helper function to allocate BRG buffers during the BRG callback function
    private static T* Malloc<T>(uint count) where T : unmanaged
    {
        //使用 Allocator.TempJob： 临时分配：只在当前帧有效  自动释放：帧结束时自动清理  性能优化：避免GC压力
        return (T*)UnsafeUtility.Malloc(
            UnsafeUtility.SizeOf<T>() * count,
            UnsafeUtility.AlignOf<T>(),
            Allocator.TempJob);
    }
    
    /// <summary>
    /// 帧都会被调用，决定哪些实例需要被渲染（可见性剔除），生成具体的绘制命令，将结果填充到 BatchCullingOutput 中
    /// </summary>
    /// <param name="rendererGroup">BRG对象引用</param>
    /// <param name="cullingContext">剔除上下文（相机、视锥体等信息）</param>
    /// <param name="cullingOutput"输出结果容器></param>
    /// <param name="userContext">用户自定义上下文</param>
    /// <returns>返回值: JobHandle 用于支持异步执行（当前实现是同步的）</returns>
    [BurstCompile]
    public JobHandle OnPerformCulling(BatchRendererGroup rendererGroup, BatchCullingContext cullingContext, BatchCullingOutput cullingOutput, IntPtr userContext)
    {
        if (m_Initialized)
        {
            BatchCullingOutputDrawCommands drawCommands = new BatchCullingOutputDrawCommands();

            //计算需要多少个绘制命令(UBO模式: 由于常量缓冲区大小限制，需要分割成多个窗口，每个窗口一个绘制命令,通常只需要一个绘制命令，但代码保持统一)
            int drawCommandCount = (m_InstanceCount + m_MaxInstancePerWindow - 1) / m_MaxInstancePerWindow;
            int maxInstancePerDrawCommand = m_MaxInstancePerWindow;
            drawCommands.drawCommandCount = drawCommandCount;

            //创建绘制范围
            drawCommands.drawRangeCount = 1;
            drawCommands.drawRanges = Malloc<BatchDrawRange>(1);
            
            //BatchDrawRange 的作用： 将多个绘制命令组织成一个逻辑单元  设置统一的渲染设置（阴影、层级等）  所有绘制命令共享相同的过滤设置  控制哪些对象参与特定的渲染通道
            drawCommands.drawRanges[0] = new BatchDrawRange
            {
                drawCommandsBegin = 0,                      // 绘制命令起始索引
                drawCommandsCount = (uint)drawCommandCount, // 绘制命令数量
                filterSettings = new BatchFilterSettings
                {
                    renderingLayerMask = 1,                 // 渲染层掩码，控制在哪些渲染层可见
                    layer = 0,                              //  Unity的游戏对象层级
                    motionMode = MotionVectorGenerationMode.Camera,// 运动向量生成模式，用于运动模糊等效果
                    shadowCastingMode = m_CastShadows ? ShadowCastingMode.On : ShadowCastingMode.Off,   //阴影投射模式
                    receiveShadows = true,                  // 接收阴影
                    staticShadowCaster = false,             // 非静态阴影投射
                    allDepthSorted = false                  // 不需要深度排序
                }
            };

            if (drawCommands.drawCommandCount > 0)
            {
                //视锥体剔除:
                //构建可见性数组，在不需要剔除的情况下填充 {0,1,2,3,...}
                //可见性数组的作用： 记录哪些实例需要被渲染  数组中的每个值是实例的索引  当前实现中，所有实例都被认为是可见的
                int visibilityArraySize = maxInstancePerDrawCommand;
                if (m_InstanceCount < visibilityArraySize)
                    visibilityArraySize = m_InstanceCount;
                drawCommands.visibleInstances = Malloc<int>((uint)visibilityArraySize);
                //为什么使用 {0,1,2,3...}？  简化实现：不进行复杂的视锥体剔除   性能考虑：避免剔除计算开销  适用场景：适合大量小对象的场景
                for (int i = 0; i < visibilityArraySize; i++)
                    drawCommands.visibleInstances[i] = i;

                //生成具体的绘制命令  使用Malloc<T>()分配非托管内存,避免GC压力,提高性能,需要在其他地方手动释放内存
                drawCommands.drawCommands = Malloc<BatchDrawCommand>((uint)drawCommandCount);
                int left = m_InstanceCount;
                //计算当前批次应该包含多少个实例
                //创建对应的BatchDrawCommand
                //更新剩余实例数量
                for (int b = 0; b < drawCommandCount; b++)
                {
                    int inBatchCount = left > maxInstancePerDrawCommand ? maxInstancePerDrawCommand : left;
                    drawCommands.drawCommands[b] = new BatchDrawCommand
                    {
                        visibleOffset = (uint)0,            //可见性数组的偏移量
                        visibleCount = (uint)inBatchCount,  //当前批次的实例数量
                        batchID = m_BatchIDs[b],            //对应的批次ID，用于在GPU端索引数据
                        materialID = m_MaterialID,          //材质ID
                        meshID = m_MeshID,                  //网格ID
                        submeshIndex = 0,                   //子网格索引（通常为0）
                        splitVisibilityMask = 0xff,         //分割可见性掩码（0xff表示全部可见）
                        flags = BatchDrawCommandFlags.None, //绘制标志
                        sortingPosition = 0                 //排序位置（用于透明物体排序）
                    };
                    left -= inBatchCount;
                }
            }
            //完成输出设置
            cullingOutput.drawCommands[0] = drawCommands;
            drawCommands.instanceSortingPositions = null;
            drawCommands.instanceSortingPositionFloatCount = 0;
        }

        return new JobHandle();
    }
}
